package com.ubs.lingua.buildtool.cs.utils;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

/**
 * Public constants and a set of utility methods to facilitates operations on the working repository.
 */
public final class RepositoryUtils {

    /**
     * The file name of ZIP archive which is created in the working repository for each build request.
     */
    public static final String PACKAGE_FILE_NAME = "sourcePackage.zip";

    /**
     * Name of the directory which is created when the source package is unpacked.
     */
    public static final String UNPACK_DIR = "unpacked";

    /**
     * Name of the directory in the source package which should store the dependencies.
     */
    public static final String DEPS_LIBS_DIR = "lib";
    /**
     * Name of the directory in the source package which should the store source code.
     */
    public static final String SRC_DIR = "src";
    /**
     * Name of the directory which will the store the compiled assembly and all extras.
     */
    public static final String ASSEMBLY_DIR = "bin";

    /**
     * Name of the ZIP file which will store the compiled assembly and all extras. 
     */
    public static final String ASSEMBLY_PKG = "package.zip";
    /**
     * Name of the XML documentation file.
     */
    public static final String ASSEMBLY_DOC = "documentation.xml";

    private RepositoryUtils() {
        // should not be instantiated
    }

    /**
     * Checks that the directory does not exist and creates it.
     * @param path path to the directory
     * @return <code>File</code> object for the directory
     * @throws IOException if directory already exist or there were any problem creating it
     * @see #ensureUnique(File)
     */
    public static File ensureUnique(final String path) throws IOException {
        final Resource res = new FileSystemResource(path);
        return ensureUnique(res.getFile());
    }

    /**
     * Checks that the directory does not exist and creates it.
     * @param file <code>File</code> object for the directory
     * @return <code>File</code> object for the directory
     * @throws IOException if directory already exist or there were any problem creating it
     * @see #ensureUnique(String)
     */
    public static File ensureUnique(final File file) throws IOException {
        synchronized (RepositoryUtils.class) {
            if (file.exists()) {
                throw new IOException("Directory already exist: " + file);
            }
            if (!file.mkdirs()) {
                throw new IOException("Unable to create directory: " + file);
            }
            return file;
        }
    }

    /**
     * Deletes a file, never throwing an exception.
     * If file is a directory, delete it and all sub-directories. 
     * @param path path to the file or directory to delete
     * @return <code>true</code> if the file or directory was deleted, otherwise <code>false</code>
     * @see #deleteQuietly(File)
     */
    public static boolean deleteQuietly(final String path) {
        return deleteQuietly(new FileSystemResource(path).getFile());
    }

    /**
     * Deletes a file, never throwing an exception.
     * If file is a directory, delete it and all sub-directories. 
     * @param file the file or directory to delete
     * @return <code>true</code> if the file or directory was deleted, otherwise <code>false</code>
     * @see #deleteQuietly(String)
     */
    public static boolean deleteQuietly(final File file) {
        try {
            FileUtils.deleteDirectory(file);
            return true;
        }
        catch (IOException ex) {
            return false;
        }
    }

    /**
     * Constructs proper path to the build batch from the following items:
     * the working repository path, unique build batch ID and (optionally)
     * file name of ZIP archive which is created in the working repository
     * for each build request. 
     * @param repositoryPath the working repository path
     * @param batchId the unique build batch ID
     * @param sourcePackageName file name of ZIP archive which is created in the working repository
     * @return path to the build batch
     */
    public static String getBatchPath(final String repositoryPath, final String batchId, final String sourcePackageName) {
        if (CommonUtils.isNullOrEmpty(repositoryPath) || CommonUtils.isNullOrEmpty(batchId)) {
            throw new IllegalArgumentException("The Repository path and Batch ID could not be null or empty");
        }
        final StringBuilder sbuilder = new StringBuilder(repositoryPath);
        sbuilder.append("/");
        sbuilder.append(batchId);
        if (!CommonUtils.isNullOrEmpty(sourcePackageName)) {
            sbuilder.append("/");
            sbuilder.append(sourcePackageName);
        }

        return sbuilder.toString();
    }

    /**
     * List of mandatory directories in the unpacked source package
     */
    private static final List<String> PACKAGE_ITEMS = Arrays.asList(new String[] { DEPS_LIBS_DIR, SRC_DIR });

    /**
     * Verifies that the <code>directory</code> points to the unpacked source package.
     * @param directory the directory to check  
     * @return <code>true</code> if the directory contains the unpacked source package, otherwise <code>false</code>
     * @see #findPackageRoot(File)
     */
    public static boolean isPackage(final File directory) {
        if (!directory.exists() || !directory.isDirectory()) {
            return false;
        }
        final List<String> children = Arrays.asList(directory.list());
        // 1. lib and src items are present in the package 
        if (children.containsAll(PACKAGE_ITEMS)) {
            // 2. checking contents
            final File lib = new File(directory, DEPS_LIBS_DIR);
            final File src = new File(directory, SRC_DIR);
            // 2.1. lib and src should be directories
            if (!lib.isDirectory() || !src.isDirectory()) {
                return false;
            }
            // 2.2. at least one dependency and at least one code item should be available
            @SuppressWarnings("unchecked")
            final Collection<File> libs = FileUtils.listFiles(lib, FileFilterUtils.suffixFileFilter(".dll"), null);
            @SuppressWarnings("unchecked")
            final Collection<File> sources = FileUtils.listFiles(src, FileFilterUtils.suffixFileFilter(".cs"),
                                                                 FileFilterUtils.trueFileFilter());
            if (null == libs || libs.size() == 0 || null == sources || sources.size() == 0) {
                return false;
            }
            return true;
        }
        return false;
    }

    /**
     * Scans for the unpacked source package in arbitrary directories structure.
     * @param directory the root directory to check
     * @return <code>File</code> object for the directory where the unpacked source package is located
     * @see #isPackage(File)
     */
    public static File findPackageRoot(final File directory) {
        if (directory.isDirectory()) {
            if (isPackage(directory)) {
                return directory;
            }

            final String[] children = directory.list();
            for (int i = 0; i < children.length; i++) {
                final File root = findPackageRoot(new File(directory, children[i]));
                if (root != null) {
                    return root;
                }
            }
        }
        return null;
    }

}
